/*
	Simple Software Renderer (SSRenderer)
	Copyright (C) 2007 Piotr Orzechowski <piotr.orzechowski@gmail.com>

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#ifndef _PROFILER_H
#define _PROFILER_H

#include <map>
#include <string>
#include "./HPTimer.h"

#ifdef _DEBUG
	#define PROFILE_BEGIN(x)		util::Profiler::GetInstance().BeginSample(x);
	#define PROFILE_END				util::Profiler::GetInstance().EndSample();
	#define PROFILE_START_LOOP		util::Profiler::GetInstance().StartProfileLoop();
	#define PROFILE_END_LOOP		util::Profiler::GetInstance().StopProfileLoop();
	#define PROFILE_GET_OUTPUT(x)	util::Profiler::GetInstance().GetOutput(x);
#else
	#define PROFILE_BEGIN(x)
	#define PROFILE_END
	#define PROFILE_START_LOOP
	#define PROFILE_END_LOOP
	#define PROFILE_GET_OUTPUT(x)
#endif

namespace util
{

// Pre-declarations
class Profiler;

/**
	ProfileSample describes a single profiler sample.
*/
struct ProfileSample
{
	friend class Profiler;

	/** Length of a name identificator used in profiler output string */
	enum { NAME_LENGTH = 12 };

public:

	ProfileSample()
	{
		name[0] = '\0';
		timesCalled = 0;
		startTime = durationWithChildren = durationWithoutChildren = 0.0f;
		avrDurPercent = avrAccumulator = 0.0f;
		minDurPercent = 100.0f;
		maxDurPercent = 0.0f;
		pPrev = pNext = pChild = pRoot = NULL;
	}

	~ProfileSample()
	{
	}

	char name[NAME_LENGTH];

	float startTime;

	float durationWithChildren;
	float durationWithoutChildren;

	float minDurPercent;
	float maxDurPercent;
	float avrDurPercent;

	unsigned int timesCalled;

private:

	float avrAccumulator;

	ProfileSample *pPrev;
	ProfileSample *pNext;
	ProfileSample *pChild;
	ProfileSample *pRoot;
};

/**
	Profiler is a singleton class used to measure and output
	performance statistics of the application.

	Profiler samples graph (example):

						 [-root-] N-tree
						/   |    \
					   /    |     \
					  /     |      \
					 /      |       \
		... <->	render <-> AI <->   sex <-> ... // doubly-linked list
				           / \      /  \
			   	   N-tree /   \    /    \  N-tree
					   (...)(...)(...) (...)

*/
class Profiler
{
public:

	/**
		Returns a reference to the only instance of the Profiler.

		@return reference to the instance of the Profiler
	*/
	static Profiler& GetInstance()
	{
		if (m_pInstance == NULL)
		{
			m_pInstance = new Profiler();
		}
		return *m_pInstance;
	}

	/**
		Begins new profiling loop.
	*/
	void StartProfileLoop();

	/**
		Ends current profiling loop.
	*/
	void StopProfileLoop();

	/**
		Begins new profiler sample.

		@param name unique name of a code sample
	*/
	void BeginSample(std::string name);

	/**
		Ends current profiler sample.
	*/
	void EndSample();

	/**
		Selects previous sample.
	*/
	void PrevSample();

	/**
		Selects next sample.
	*/
	void NextSample();

	/**
		Selects root node of the sample.
	*/
	void RootSample();

	/**
		Selects child node of the sample.
	*/
	void ChildSample();

	/**
		Selects a sample of the given name (if exists).

		@param name name of the sample
	*/
	void SelectSample(const char* name);

	/**
		Returns currently selected sample.

		@return currently selected sample
	*/
	const ProfileSample* GetSelectedSample();

	/**
		Returns a sample of the given name (if exists).

		@param name name of the sample

		@return pointer to the sample, NULL if sample of the given name doesnt exist
	*/
	const ProfileSample* GetSample(const char* name);

	/**
		Returns NULL-ended string representing results of profiling.

		@param reference to a pointer
	*/
	void GetOutput(const char* &buffer);

private:

	Profiler()
	{
		// Initialize the Profiler
		m_Timer.Initialize();
		m_ProfCount = 0;
		m_pTempRoot = m_pTempPrev = NULL;

		// Add the root node
		ProfileSample &root = m_Samples["-root-"];
		root.pPrev = root.pNext = &root;
		///@todo should make some facade for sprintf (!) - jakies problemy na visualu
		::sprintf(root.name, "root       ");

		m_pSelected = &root;
	}

	Profiler(Profiler &profiler) {}
	~Profiler()	{ delete m_pInstance; }
	Profiler& operator=(Profiler&) { return *this; }

	/** The only instance of the Profiler */
	static Profiler *m_pInstance;

	typedef std::map<std::string, ProfileSample> profmap;

	/** Map containing all profiler samples */
	profmap m_Samples;

	/** Timer used by the profiler */
	HPTimer m_Timer;

	/** Time of the profiling loop start */
	float m_LoopStartTime;

	/** Number of samples being profiled at the moment */
	int m_ProfCount;

	/** Temporary root node (current sample) */
	ProfileSample* m_pTempRoot;

	/** Temporary list node (previous sample) */
	ProfileSample* m_pTempPrev;

	/** Pointer to the selected sample */
	ProfileSample *m_pSelected;
};

} // namespace

#endif
